﻿/*
 * Twipler - Twitter Web Client with features.
 *  
 *  Copyright (C) 2009, 2010. Ian Quigley
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details. 
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */

using System;
using System.Text;
using System.Collections.Generic;

namespace Twipler.App.Parse
{
    public class ParseError
    {
        public int Position;
        public string Message;
    }

    public class StringParser
    {
        private string _contenttolower;
        public string Content;
        
        public int Index { get; private set; }

        public List<ParseError> Errors { get; private set; }

        public StringParser(string content)
        {
            Content = content;
            _contenttolower = content.ToLower();
            Index = 0;
            Errors = new List<ParseError>();
        }

        public StringParser(StringParser switchFrom)
        {
            Digest(switchFrom);
        }

        public void Digest(StringParser switchFrom)
        {
            Content = switchFrom.Content;
            _contenttolower = switchFrom._contenttolower;
            Index = switchFrom.Index;
            Errors = switchFrom.Errors;
        }

        public bool HasErrors
        {
            get
            {
                return Errors.Count > 0;
            }
        }
        public void AddError(string message)
        {
            AddError(Index, message);
        }

        public void AddError(int position, string message)
        {
            Errors.Add(new ParseError() { Position = position, Message = message });
        }

        public bool IsEmpty { get { return Index == Content.Length; } }
        public char NextChar { get { return Content[Index]; } }
        public char PrevChar { get { return Content[Index - 1]; } } 

        public string ConsumeUpTo(int index)
        {
            if (index == -1)
                index = Content.Length;

            string consume = Content.Substring(Index, index - Index);
            Index = index;

            return consume;
        }

        public string ConsumeToEnd()
        {
            return ConsumeToEndOrMarker(string.Empty);
        }

        public string ConsumeToEndOrMarker(string text)
        {
            int index = Content.IndexOf(text, Index);
            if (index == -1)
                index = Content.Length;

            string consume = Content.Substring(Index, index - Index);
            Index = index + text.Length;

            if (Index > Content.Length)
                Index = Content.Length;

            return consume;
        }

        public string Remains()
        {
            string content = Content.Substring(Index);
            Index = Content.Length;
            return content;
        }

        public int Find(string text)
        {
            return Find(text, false);
        }

        public int Find(string text, bool ignoreCase)
        {
            if (ignoreCase)
                return _contenttolower.IndexOf(text.ToLower(), Index);
            
            return Content.IndexOf(text, Index);
        }

        public int Find(int startIndex, string text, bool ignoreCase)
        {
            if (ignoreCase)
                return _contenttolower.IndexOf(text.ToLower(), startIndex);
            
            return Content.IndexOf(text, startIndex);
        }
        
        public int FindLast(string text)
        {
            return FindLast(text, false);
        }

        public int FindLast(string text, bool ignoreCase)
        {
            if (ignoreCase)
                return _contenttolower.LastIndexOf(text.ToLower(), Index);
            
            return Content.LastIndexOf(text, Index);
        }

        public void Rewind(string rewind)
        {
            Rewind(rewind.Length);
        }

        public void Rewind(int length)
        {
            Index -= length;
            if (Index < 0)
                Index = 0;
        }

        public bool Peek(string[] peek, bool ignoreCase)
        {
            if (peek == null)
                return false;

            foreach (string test in peek)
                if (Peek(test, ignoreCase))
                    return true;

            return false;
        }

        public bool Peek(string peek, bool ignoreCase)
        {
            if (Index + peek.Length >= Content.Length)
                return false;

            return String.Compare(Content.Substring(Index, peek.Length), peek, ignoreCase) == 0;
        }

        public int SkipOver(string text)
        {
            return SkipOver(text, false);
        }

        public int SkipOver(string text, bool ignoreCase)
        {
            int index;            
            if (ignoreCase)
                index = _contenttolower.IndexOf(text.ToLower(), Index);
            else
                index = Content.IndexOf(text, Index);
            
            if (index != -1)
                Index = index + text.Length;

            return Index;
        }
        
        public bool SkipCharIgnoreWhitespace(char c)
        {
            SkipWhiteSpace();
            return SkipChar(c);
        }

        public bool SkipChar(char c)
        {
            if ((Index < Content.Length) && (Content[Index] == c))
            {
                Index++;
                return true;
            }

            return false;
        }

        public bool SkipChar()
        {
            if (Index < Content.Length)
                Index++;

            return true;
        }

        public void SkipWhiteSpace()
        {
            while ((Index < Content.Length) && Char.IsWhiteSpace(Content[Index])) { Index++; }
        }
            
        public string TextBeforeNext(bool whiteSpace, string marker)
        {
            int nextWhitespace = -1;
            if (whiteSpace)
                nextWhitespace = Content.IndexOfAny(new char[] { ' ', '\t', '\n', '\r' }, Index);

            int nextMarker = -1;
            if (string.IsNullOrEmpty(marker) == false)
                nextMarker = Content.IndexOf(marker, Index);

            if ((nextWhitespace == -1) && (nextMarker == -1))
                nextWhitespace = Content.Length;

            if ((nextWhitespace == -1) || (nextMarker == -1))            
                nextMarker = nextWhitespace == -1 ? nextMarker : nextWhitespace;
            else
                nextMarker = nextWhitespace < nextMarker ? nextWhitespace : nextMarker;

            string text = Content.Substring(Index, nextMarker - Index);
            Index += text.Length;

            return text;
        }


        public bool NextCharIs(char c)
        {
            return (!IsEmpty && (Content[Index] == c));
        }

        public bool NextCharIs(Func<char, bool> characterTest)
        {
            return (!IsEmpty && (characterTest(Content[Index])));
        }

        public string NextCharRead()
        {
            return IsEmpty ? string.Empty : Content.Substring(Index++, 1);
        }

        public string NextWord()
        {
            return NextWordUpto(new char[] { ' ', '\t', '\n', '\r' });
        }

        public string NextWord(char c)
        {
            return NextWordUpto(new char[] { ' ', '\t', '\n', '\r', c });
        }

        public string NextWord(char c, char d)
        {
            return NextWordUpto(new char[] { ' ', '\t', '\n', '\r', c, d });
        }

        private string NextWordUpto(char[] uptoAnyChar)
        {
            SkipWhiteSpace();

            int nextWhitespace = Content.IndexOfAny(uptoAnyChar, Index);

            if (nextWhitespace == -1)
                nextWhitespace = Content.Length;

            string text = Content.Substring(Index, nextWhitespace - Index);
            Index += text.Length;

            return text;
        }

        

        public string ReadWhile(Func<char, bool> characterTest)
        {
            int startPoint = Index;
            while ((Index < Content.Length) && (characterTest(Content[Index]))) { Index++; }

            return Content.Substring(startPoint, Index - startPoint);
        }

        protected string ReadWhileInWhiteList(string whiteList)
        {
            int startPoint = Index;
            while ((Index < Content.Length) && (whiteList.IndexOf(Content[Index]) != -1)) { Index++; }

            return Content.Substring(startPoint, Index - startPoint);
        }

        protected string ReadQuotedString()
        {
            // Starts with " and has another " in there somewhere.
            if ((Content[Index] != '"') || (Content.IndexOf('"', Index + 1) == -1))
                return null;

            int index = Index + 1;

            int nextIndexOfQuote = Content.IndexOf('"', index);
            int nextEscapeSlash = Content.IndexOf("\\", index);

            // Case: " blah " 
            // or  : " blah " ... \ 
            if ((nextEscapeSlash == -1) || (nextEscapeSlash > nextIndexOfQuote))
            {
                string result = Content.Substring(index, nextIndexOfQuote - index );
                Index = nextIndexOfQuote + 1;
                return result;
            }

            
            // Cases with \ in them
            StringBuilder output = new StringBuilder();
            
            while ((nextEscapeSlash != -1) && (index < Content.Length))
            {
                // Eat everything upto the \
                output.Append(Content.Substring(index, nextEscapeSlash - index));
                index = nextEscapeSlash;

                // Followed by a \ or ", then output that char. (ie: \\ => \ and \" => " )
                if ((Content[nextEscapeSlash + 1] == '\\') || (Content[nextEscapeSlash + 1] == '"'))
                {
                    output.Append(Content[nextEscapeSlash + 1]);
                    index++;
                }
                index++;

                nextEscapeSlash = Content.IndexOf('\\', index);
                nextIndexOfQuote = Content.IndexOf('"', index);

                if ((nextEscapeSlash == -1) || (nextEscapeSlash > nextIndexOfQuote))
                {
                    output.Append(Content.Substring(index, nextIndexOfQuote - index));
                    Index = nextIndexOfQuote + 1;
                    return output.ToString();
                }
            }

            return null;

        }

        internal string FindNext(bool ignoreCase, out int location, params string[] strings)
        {
            int index = 0;
            int lowest = Content.Length;
            
            for (int i = 0; i < strings.Length; i++)
            {
                location = Find(strings[i], ignoreCase);
                if ((location != -1) && (location < lowest))
                {
                    lowest = location;
                    index = i;
                }
            }

            location = lowest;
            if (lowest == -1)
                return null;

            // return the string you gave me, or return the string found in the text
            return ignoreCase ? strings[index] : Content.Substring(location, strings[index].Length);
        }
    }
}